using System;
using System.Collections.Generic;
using Gj.Galaxy.Logic;
using UnityEngine;

namespace Gj.MapData
{
    public interface MapDataLocation
    {
        bool Contains(Vector2Int position);
    }

    public interface MapDataNotice
    {
        void OnReady();
        void OnSwitchMap(string mapNoId);
        void OnSwitchLevel(byte level);
    }

    public class Render : CooperateRenderListener
    {
        [Flags]
        public enum CenterDirection
        {
            Center,
            Top = 1 << 0,
            Bottom = 1 << 1,
            Left = 1 << 2,
            Right = 1 << 3,
            LeftTop = Top + Left,
            RightTop = Top + Right,
            LeftBottom = Bottom + Left,
            RightBottom = Bottom + Right
        }

        public Vector2Int _block;
        public int _level;
        public Vector2Int _levelBlock;

        private readonly List<RectInt> addLocation = new();

        public List<Vector2Int> currentCenter;

        private Vector2Int currentPosition;
        private Vector2Int currentSize;
        internal MapDataLocation Location;

        public int mode;
        internal MapDataNotice Notice;

        private int preLevel;
        private Vector2Int prePosition = Vector2Int.zero;
        private Vector2Int preSize = Vector2Int.zero;

        private Vector2Int prevPosition;
        private Vector2Int prevSize;
        private readonly List<RectInt> removeLocation = new();
        public List<Vector2Int> tmpCenter;

        public Render(int gap, MapDataNotice notice)
        {
            CacheGap = gap;

            currentCenter = new List<Vector2Int>();

            tmpCenter = new List<Vector2Int>();

            Notice = notice;
        }

        public int CacheGap { get; set; }

        public void BindBlock(Vector2Int block)
        {
            _block = block;
            _levelBlock = new Vector2Int(_block.x * _level, _block.y * _level);
        }

        public virtual void OnReady(bool reconnect)
        {
            if (reconnect)
            {
                currentCenter.Clear();
                if (preSize == Vector2Int.zero)
                {
                    preSize = currentSize;
                    currentSize = Vector2Int.zero;
                }
            }

            _level = 0;
            LogTools.Info("[ Render ] onReady" + ":" + preSize);

            // if (preSize != Vector2Int.zero)
            // {
            //     var tmpSize = preSize;
            //     preSize = Vector2Int.zero;
            //     ListenerNode(prePosition, tmpSize.x, tmpSize.y);
            // }

            Notice?.OnReady();
        }

        public void SwitchMap(string mapNoId)
        {
            // LogTools.Info("[ Render ] change map" + mapNoId + ":" + preSize);
            // Clear();
            // if (preSize != Vector2Int.zero && _level > 0)
            // {
            //     var tmpSize = preSize;
            //     preSize = Vector2Int.zero;
            //     ListenerNode(prePosition, tmpSize.x, tmpSize.y);
            // }

            // Notice?.OnSwitchMap(mapNoId);
        }

        public void SwitchLevel(byte level)
        {
            if (level == _level)
            {
                Notice?.OnSwitchLevel(level);
                return;
            }

            ;
            OnUpdateLevelBefore();
            // var tmp = _level;
            _level = level;
            _levelBlock = new Vector2Int(_block.x * _level, _block.y * _level);
            LogTools.Info("[ Render ] change level" + _level + ":" + _levelBlock + currentPosition + ":" + preSize);
            currentCenter.Clear();
            // if (preSize != Vector2Int.zero)
            // {
            //     // Debug.LogError("[ Render ] switch level:"+":"+_level+":"+_levelBlock+prePosition+":"+preSize);
            //     var tmpSize = preSize;
            //     preSize = Vector2Int.zero;
            //     ListenerNode(prePosition, tmpSize.x, tmpSize.y);
            // }
            // Notice?.OnSwitchLevel(level);
            // OnUpdateLevelAfter();
        }

        public void OnSwitchMode(int mode)
        {
            LogTools.Info("[ Render ] on change mode" + mode + ":" + preSize);
            this.mode = mode;
        }

        public void OnSwitchMap(string mapNoId)
        {
            LogTools.Info("[ Render ] on change map" + mapNoId + ":" + preSize);
            Clear();
            if (preSize != Vector2Int.zero)
            {
                var tmpSize = preSize;
                preSize = Vector2Int.zero;
                ListenerNode(prePosition, tmpSize.x, tmpSize.y);
            }

            Notice?.OnSwitchMap(mapNoId);
        }

        public void OnSwitchLevel(byte level)
        {
            // if (level == _level)
            // {
            //     Notice?.OnSwitchLevel(level);
            //     return;
            // };
            // OnUpdateLevelBefore();
            // var tmp = _level;
            _level = level;
            _levelBlock = new Vector2Int(_block.x * _level, _block.y * _level);
            LogTools.Info("[ Render ] on change level" + _level + ":" + _levelBlock + currentPosition + ":" +
                          currentSize + ":" + preSize);
            currentCenter.Clear();
            if (level == preLevel && preSize != Vector2Int.zero)
                try
                {
                    LogTools.Info("[ Render ] switch level:" + ":" + _level + ":" + _levelBlock + prePosition + ":" +
                                  preSize);
                    var tmpSize = preSize;
                    preSize = Vector2Int.zero;
                    ListenerNode(prePosition, tmpSize.x, tmpSize.y);
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }

            Notice?.OnSwitchLevel(level);
            OnUpdateLevelAfter();
        }

        public void Leave()
        {
            Clear();
        }

        public void Reload(int gap, MapDataNotice notice)
        {
            CacheGap = gap;
            Notice = notice;
        }

        public void BindLocation(MapDataLocation location)
        {
            Location = location;
        }

        public void OnAddLocation(RectInt rectInt)
        {
            addLocation.Add(rectInt);
        }

        public void OnRemoveLocation(RectInt rectInt)
        {
            removeLocation.Add(rectInt);
        }

        protected virtual void Clear()
        {
        }

        protected virtual void OnUpdateLevelBefore()
        {
        }

        protected virtual void OnUpdateLevelAfter()
        {
        }

        public bool ContainLevel(int range)
        {
            if (_level == 0) return false;
            if (range == 0 && _level == 1) return true;
            return (range & (1 << (_level - 1))) > 0;
        }

        public void OnChangeLocation()
        {
            tmpCenter.Clear();
            tmpCenter.AddRange(currentCenter);
            var e = tmpCenter.GetEnumerator();
            // Debug.Log("OnChangeLocation:"+currentCenter);
            while (e.MoveNext())
            {
                var rect = CenterToRect(e.Current);

                for (var i = 0; i < removeLocation.Count; i++)
                    // Debug.Log("removeLocation: "+e.Current+"("+rect+")"+":"+removeLocation[i]+":"+rect.Overlaps(removeLocation[i]));
                    if (UnityTools.Overlaps(rect, removeLocation[i]))
                    {
                        // Debug.Log("removeLocation: "+e.Current+":"+removeLocation[i]+":"+rect.Overlaps(removeLocation[i]));
                        RemoveLocation(e.Current);
                        break;
                    }

                for (var i = 0; i < addLocation.Count; i++)
                    // Debug.Log("addLocation: "+e.Current+"("+rect+")"+":"+addLocation[i]+":"+rect.Overlaps(addLocation[i]));
                    if (UnityTools.Overlaps(rect, addLocation[i]))
                    {
                        // Debug.Log("addLocation: "+e.Current+":"+addLocation[i]+":"+rect.Overlaps(addLocation[i]));
                        AddLocation(e.Current);
                        break;
                    }
            }

            e.Dispose();
            addLocation.Clear();
            removeLocation.Clear();
        }

        protected virtual void AddLocation(Vector2Int position)
        {
        }

        protected virtual void RemoveLocation(Vector2Int position)
        {
        }

        public void PreCenterSize(Vector2Int position, int width, int height, int l = 0)
        {
            if (l > 0) preLevel = l;
            // Debug.LogError("[ Render ] set pre size:" + position + ":" + width + ":" + height + ":" + _levelBlock);
            prePosition = position;
            preSize = new Vector2Int(width, height);
        }

        public void ListenerNode(Vector2Int position, int width, int height)
        {
            if (_level == 0)
            {
                PreCenterSize(position, width, height);
                return;
            }

            var tmpSize = new Vector2Int(width, height);
            if (currentCenter.Count > 0 && currentPosition == position && currentSize == tmpSize) return;
            if (preSize != Vector2Int.zero)
            {
                PreCenterSize(position, width, height);
                return;
            }

            prePosition = position;
            // Debug.LogError("[ Render ] listen node:" + position + ":" + width + ":" + height + ":" + _levelBlock);
            prevPosition = currentPosition;
            prevSize = currentSize;

            currentPosition = position;
            currentSize = new Vector2Int(width, height);
            var rw = width / 2;
            var rh = height / 2;
            var tmp = new Vector2Int();

            tmp.x = position.x - rw;
            tmp.y = position.y - rh;
            var center = ComputeBlock(tmp, CenterDirection.LeftBottom);
            // Debug.LogErrorFormat("[ Render ] register center:{0} contains:{1} ({2})", center, currentCenter.Contains(center), currentCenter.Count);
            if (!currentCenter.Contains(center)) ListenerNode(center, position);
            currentCenter.Add(center);

            tmp.x = position.x - rw;
            tmp.y = position.y + rh;
            center = ComputeBlock(tmp, CenterDirection.LeftTop);
            // Debug.LogErrorFormat("[ Render ] register center:{0} contains:{1} ({2})", center, currentCenter.Contains(center), currentCenter.Count);
            if (!currentCenter.Contains(center)) ListenerNode(center, position);
            currentCenter.Add(center);

            tmp.x = position.x + rw;
            tmp.y = position.y - rh;
            center = ComputeBlock(tmp, CenterDirection.RightBottom);
            // Debug.LogErrorFormat("[ Render ] register center:{0} contains:{1} ({2})", center, currentCenter.Contains(center), currentCenter.Count);
            if (!currentCenter.Contains(center)) ListenerNode(center, position);
            currentCenter.Add(center);

            tmp.x = position.x + rw;
            tmp.y = position.y + rh;
            center = ComputeBlock(tmp, CenterDirection.RightTop);
            // Debug.LogErrorFormat("[ Render ] register center:{0} contains:{1} ({2})", center, currentCenter.Contains(center), currentCenter.Count);
            if (!currentCenter.Contains(center)) ListenerNode(center, position);
            currentCenter.Add(center);
            if (currentCenter.Count > 4)
            {
                for (var i = 0; i < currentCenter.Count - 4; i++)
                {
                    var flag = true;
                    for (var j = currentCenter.Count - 4; j < currentCenter.Count; j++)
                        if (currentCenter[i] == currentCenter[j])
                            flag = false;

                    // Debug.LogErrorFormat("[ Chess ] last register {0}=>{1} {2}:{3}", i, currentCenter[i], currentCenter.Count, flag);
                    if (flag) RemoveNode(currentCenter[i]);
                }

                currentCenter.RemoveRange(0, currentCenter.Count - 4);
                // Debug.LogErrorFormat("[ Render ] listen node: {0}->{1},{2},{3},{4}", currentCenter.Count, currentCenter[0], currentCenter[1], currentCenter[2], currentCenter[3]);
            }
        }

        protected virtual void ListenerNode(Vector2Int center, Vector2Int position)
        {
            ChessService.Register(center);

            // 缓存加载
            // PreLoadData(center, position);
        }

        protected virtual void RemoveNode(Vector2Int center)
        {
            ChessService.UnRegister(center);
        }

        internal Vector2Int ComputeBlock(Vector2Int position, CenterDirection direction = CenterDirection.Center)
        {
            if (_level == 0) return Vector2Int.zero;

            var center = new Vector2Int();

            var r = position.x / _levelBlock.x;
            var b = 1;
            if (r * _levelBlock.x == position.x && r % 2 == 0)
            {
                if (position.x > 0 && (direction & CenterDirection.Right) > 0)
                    b = -1;
                if (position.x < 0 && (direction & CenterDirection.Left) > 0)
                    b = -1;
            }

            center.x = (r + (r % 2 == 0 ? position.x >= 0 ? b : -1 * b : 0)) * _levelBlock.x;
            center.x = (r + (r % 2 == 0 ? position.x >= 0 ? b : -1 * b : 0)) * _levelBlock.x;
            // Debug.LogErrorFormat("[ Render ] {0},{1},{2},{3} => {4} ({5})", position.x, r, b, center.x, direction, _levelBlock);
            r = position.y / _levelBlock.y;
            b = 1;
            if (r * _levelBlock.y == position.y && r % 2 == 0)
            {
                if (position.y < 0 && (direction & CenterDirection.Bottom) > 0)
                    b = -1;
                if (position.y > 0 && (direction & CenterDirection.Top) > 0)
                    b = -1;
            }

            center.y = (r + (r % 2 == 0 ? position.y >= 0 ? b : -b : 0)) * _levelBlock.y;
            // Debug.LogError(position + ":" + center + "(" + _levelBlock + ")");
            return center;
        }

        // internal int ComputeIndex(Vector2Int center, Vector2Int position)
        // {
        //     // center.x -= _block.x;
        //     // center.y -= _block.x;
        //     return (position.x - (center.x - _levelBlock.x)) * _levelBlock.x * 2 + (position.y - (center.y-_levelBlock.x));
        // }
        //
        // protected void UpdateFromIndex(Vector2Int center, int index, ref Vector2Int position)
        // {
        //     // center.x -= _block.x;
        //     // center.y -= _block.x;
        //     int size = _levelBlock.x * 2;
        //     position.x = center.x - _levelBlock.x + (index - index % size) / size;
        //     position.y = center.y - _levelBlock.x + (index % size);
        // }

        protected RectInt CenterToRect(Vector2Int center)
        {
            return new RectInt(center.x - _levelBlock.x, center.y - _levelBlock.x, _levelBlock.x * 2,
                _levelBlock.y * 2);
        }

        protected void PreLoadData(Vector2Int center, Vector2Int position)
        {
            var direction = Direction(center, position, CacheGap);

            BlockDirection[] directions;
            if (BlockTools.BlockSimpleMap.TryGetValue(direction, out directions))
            {
                var tmp = Vector2Int.zero;
                for (var i = 0; i < directions.Length; i++)
                {
                    ToDirection(ref center, direction, ref tmp);
                    ChessService.Register(tmp);
                }
            }
        }

        protected void ToDirection(ref Vector2Int center, BlockDirection direction, ref Vector2Int position)
        {
            switch (direction)
            {
                case BlockDirection.Top:
                    Offset(ref center, 0, 1, ref position);
                    break;
                case BlockDirection.Bottom:
                    Offset(ref center, 0, -1, ref position);
                    break;
                case BlockDirection.Left:
                    Offset(ref center, -1, 0, ref position);
                    break;
                case BlockDirection.Right:
                    Offset(ref center, 1, 0, ref position);
                    break;
                case BlockDirection.LeftTop:
                    Offset(ref center, -1, 1, ref position);
                    break;
                case BlockDirection.LeftBottom:
                    Offset(ref center, -1, -1, ref position);
                    break;
                case BlockDirection.RightTop:
                    Offset(ref center, 1, 1, ref position);
                    break;
                case BlockDirection.RightBottom:
                    Offset(ref center, 1, -1, ref position);
                    break;
                default:
                    Offset(ref center, 0, 0, ref position);
                    break;
            }
        }

        protected void Offset(ref Vector2Int center, int x, int y, ref Vector2Int position)
        {
            position.x = center.x + x * _block.x * 2;
            position.y = center.y + y * _block.x * 2;
        }

        protected BlockDirection Direction(Vector2Int center, Vector2Int position, int size)
        {
            var direction = BlockDirection.Center;
            var e = size - CacheGap;
            if (center.x - e >= position.x)
                direction |= BlockDirection.Left;
            else if (center.x + e <= position.x) direction |= BlockDirection.Right;

            e = size - CacheGap;
            if (center.y - e >= position.y)
                direction |= BlockDirection.Bottom;
            else if (center.y + e <= position.y) direction |= BlockDirection.Top;

            return direction;
        }
    }
}